package com.br.aiops.rule.db;

import com.br.aiops.alert.AlertSender;
import com.br.aiops.rule.Rule;
import com.br.aiops.utils.Constants;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.googlecode.aviator.AviatorEvaluator;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;

import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

@Service
public class RuleService {

    private static final Logger LOGGER = LoggerFactory.getLogger(RuleService.class);
    private static final Set<Integer> durations = Sets.newHashSet(Constants.SCHEDULE_ONE_MINUTE,
            Constants.SCHEDULE_TWO_MINUTE, Constants.SCHEDULE_FIVE_MINUTE);

    @Value("${env.profile}")
    private String env;

    @Autowired
    private RuleRepository ruleRepository;

    @Autowired
    private AlertSender sender;

    public List<Rule> getRules() {
        return ruleRepository.findAll(env).stream()
                .filter(this::validate)
                .map(ruleDTO -> {
            Rule rule = Rule.builder()
                    .id(ruleDTO.getId())
                    .regex(ruleDTO.getRegex())
                    .expr(ruleDTO.getExpr())
                    .judgeType(ruleDTO.getJudgeType())
                    .duration(ruleDTO.getDuration())
                    .times(ruleDTO.getTimes())
                    .service(ruleDTO.getService())
                    .errorCode(ruleDTO.getErrorCode())
                    .ladderType(ruleDTO.getLadderType())
                    .durStep(ruleDTO.getDurStep())
                    .timesStep(ruleDTO.getTimesStep())
                    .resumeTimes(ruleDTO.getResumeTimes())
                    .pattern(Pattern.compile(ruleDTO.getRegex()))
                    .serviceId(ruleDTO.getServiceId())
                    .sendType(ruleDTO.getSendType())
                    .alertLevel(ruleDTO.getAlertLevel())
                    .alertType(ruleDTO.getAlertType())
                    .errorDesc(ruleDTO.getErrorDesc())
                    .repairSteps(ruleDTO.getRepairSteps())
                    .env(ruleDTO.getEnv())
                    .subEnv(ruleDTO.getSubEnv())
                    .ruleUuid(ruleDTO.getRuleUuid())
                    .remark(ruleDTO.getRemark())
                    .receiverType(ruleDTO.getReceiverType())
                    .template(ruleDTO.getTemplate())
                    .wechatTemplate(ruleDTO.getWechatTemplate())
                    .smsTemplate(ruleDTO.getSmsTemplate())
                    .build();
            //初始化表达式
            if (StringUtils.isNotBlank(ruleDTO.getExpr())) {
                rule.setExpression(AviatorEvaluator.compile(ruleDTO.getExpr()));
            }
            return rule;
        }).collect(Collectors.toList());
    }

    /**
     * 对配置数据进行校验（如果该配置校验失败，则不加载）
     * @param ruleDTO
     * @return
     */
    public boolean validate(RuleDTO ruleDTO) {
        try {
            Assert.notNull(ruleDTO.getId(), "rule的ID不能为空。");
            Assert.hasText(ruleDTO.getService(), "rule的service不能为空或者空格字符。");
            Assert.hasText(ruleDTO.getErrorCode(), "rule的errorCode不能为空或者空格字符。");

            Assert.notNull(ruleDTO.getLadderType(), "rule的阶梯类型不能为空。");
            //无阶梯时验证
            if (ruleDTO.getLadderType() == Constants.LADDER_TYPE_NONE) {
                validateNoLadder(ruleDTO);
            } else if (ruleDTO.getLadderType() == Constants.LADDER_TYPE_DURATION) {
                Assert.isTrue(Objects.nonNull(ruleDTO.getDuration()) && durations.contains(ruleDTO.getDuration()), "rule的阶梯类型为持续时间时，恢复周期（duration）必填，并且必须为1,2,5中的一种。");
                Assert.hasText(ruleDTO.getDurStep(), "rule的阶梯类型为持续时间时，压制阶梯（durStep）必填。");
                validateStep(ruleDTO.getDurStep());
            } else if (ruleDTO.getLadderType() == Constants.LADDER_TYPE_TIMES) {
                Assert.isTrue(Objects.nonNull(ruleDTO.getDuration()) && durations.contains(ruleDTO.getDuration()), "rule的阶梯类型为累计次数时，恢复周期（duration）必填，并且必须为1,2,5中的一种。");
                Assert.hasText(ruleDTO.getTimesStep(), "rule的阶梯类型为累计次数时，次数阶梯（timesStep）必填。");
                validateStep(ruleDTO.getTimesStep());
                Assert.notNull(ruleDTO.getResumeTimes(), "rule的阶梯类型为累计次数时，并且压制类型为告警次数时，恢复次数(resume_times)必填。");

                int first = getFirstTimes(ruleDTO.getTimesStep());
                //resume_times必须小于times，如果>=times，则将resume_times设置为times-1
                if (ruleDTO.getResumeTimes() >= first) {
                    ruleDTO.setResumeTimes(first - 1);
                }

                //duration and times 和duration or times校验相同
            } else if (ruleDTO.getLadderType() == Constants.LADDER_TYPE_DURATION_AND_TIMES
                    || ruleDTO.getLadderType() == Constants.LADDER_TYPE_DURATION_OR_TIMES) {
                Assert.isTrue(Objects.nonNull(ruleDTO.getDuration()) && durations.contains(ruleDTO.getDuration()), "rule的阶梯类型为持续时间and（或者or）累计次数时，恢复周期（duration）必填，并且必须为1,2,5中的一种。");
                Assert.hasText(ruleDTO.getDurStep(), "rule的阶梯类型为持续时间and（或者or）累计次数时，压制阶梯（durStep）必填。");
                validateStep(ruleDTO.getDurStep());
                Assert.hasText(ruleDTO.getTimesStep(), "rule的阶梯类型为持续时间and（或者or）累计次数时，次数阶梯（timesStep）必填。");
                validateStep(ruleDTO.getTimesStep());

                //and的关系时，需要进一步校验一下
                if (ruleDTO.getLadderType() == Constants.LADDER_TYPE_DURATION_AND_TIMES) {
                    Assert.notNull(ruleDTO.getResumeTimes(), "rule的阶梯类型为持续时间and累计次数时，恢复次数(resume_times)必填。");
                    //当阶梯为and类型时：resume_times必须小于times，如果>=times，则将resume_times设置为times-1
                    int first = getFirstTimes(ruleDTO.getTimesStep());
                    //resume_times必须小于times，如果>=times，则将resume_times设置为times-1
                    if (ruleDTO.getResumeTimes() >= first) {
                        ruleDTO.setResumeTimes(first - 1);
                    }
                    //阶梯个数必须相等
                    Assert.isTrue(stepCount(ruleDTO.getDurStep()) == stepCount(ruleDTO.getTimesStep()), "rule的阶梯类型为持续时间and累计次数时，两个阶梯数必须相等。");
                }
            } else {
                throw new IllegalArgumentException("未知阶梯类型：" + ruleDTO.getLadderType());
            }
            return true;
        } catch (Exception e) {
            LOGGER.error("rule规则校验失败。", e);
            //发送异常消息给负责人
            Rule rule = Rule.builder()
                    .id(ruleDTO.getId())
                    .regex(ruleDTO.getRegex())
                    .expr(ruleDTO.getExpr())
                    .judgeType(ruleDTO.getJudgeType())
                    .duration(ruleDTO.getDuration())
                    .times(ruleDTO.getTimes())
                    .service(ruleDTO.getService())
                    .errorCode(ruleDTO.getErrorCode())
                    .ladderType(ruleDTO.getLadderType())
                    .durStep(ruleDTO.getDurStep())
                    .timesStep(ruleDTO.getTimesStep())
                    .resumeTimes(ruleDTO.getResumeTimes())
                    .serviceId(ruleDTO.getServiceId())
                    .sendType(ruleDTO.getSendType())
                    .alertLevel(ruleDTO.getAlertLevel())
                    .build();
            sender.sendConfigErrorAlertMessage(rule, e.toString());
        }
        return false;
    }

    private int stepCount(String step) {
        if (step.contains(",")) {
            String[] steps = step.split(",");
            return steps.length;
        } else {
            return 1;
        }
    }

    private void validateStep(String step) {
        if (step.contains(",")) {
            String[] steps = step.split(",");
            for (String s : steps) {
                Assert.isTrue(parseInt(s), "阶梯数值校验失败，必须是数字类型；");
            }
        } else {
            Assert.isTrue(parseInt(step), "阶梯数值校验失败，必须是数字类型；");
        }
    }

    private boolean parseInt(String s) {
        try {
            Integer.parseInt(s);
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    public int getFirstTimes(String timesStep) {
        if (timesStep.contains(",")) {
            return Arrays.stream(timesStep.split(",")).findFirst().map(Integer::parseInt).get();
        }
        return Integer.parseInt(timesStep);
    }

    private void validateNoLadder(RuleDTO ruleDTO) {
        //验证压制类型
        Assert.notNull(ruleDTO.getJudgeType(), "rule的阶梯类型为无阶梯时，压制类型必填。");
        List<Integer> judgeTypes = Lists.newArrayList(Constants.JUDGE_TYPE_DURATION, Constants.JUDGE_TYPE_TIMES,
                Constants.JUDGE_TYPE_DURATION_AND_TIMES, Constants.JUDGE_TYPE_DURATION_OR_TIMES);
        Assert.isTrue(judgeTypes.contains(ruleDTO.getJudgeType()), "压制类型必须为以下值:" + judgeTypes
                .stream().map(Object::toString).collect(Collectors.joining(",")));

        //验证duration
        Assert.isTrue(Objects.nonNull(ruleDTO.getDuration()) && durations.contains(ruleDTO.getDuration()), "rule的阶梯类型为无阶梯时，告警周期（duration）必填，并且必须为1,2,5中的一种。");

        //包含告警次数时，times必填
        List<Integer> judgeTypesContainsTimes = Lists.newArrayList(Constants.JUDGE_TYPE_TIMES,
                Constants.JUDGE_TYPE_DURATION_AND_TIMES);
        if (judgeTypesContainsTimes.contains(ruleDTO.getJudgeType())) {
            Assert.notNull(ruleDTO.getTimes(), "rule的阶梯类型为无阶梯，并且压制类型包含告警次数时" +
                    "（times，duration and times，duration or times），告警次数必填。");
            Assert.isTrue(ruleDTO.getTimes() >= 1, "rule的阶梯类型为无阶梯，并且压制类型包含告警次数时" +
                    "（times，duration and times，duration or times），告警次数必须大于等于1。");
            Assert.notNull(ruleDTO.getResumeTimes(), "rule的阶梯类型为无阶梯，并且压制类型为告警次数时" +
                    "（times，duration and times，duration or times），恢复次数必填。");

            Assert.isTrue(ruleDTO.getResumeTimes() >= 0, "rule的阶梯类型为无阶梯，并且压制类型为告警次数时" +
                    "（times，duration and times，duration or times），恢复次数必须大于等于0。");

            if (ruleDTO.getTimes() <= ruleDTO.getResumeTimes()) {
                ruleDTO.setResumeTimes(ruleDTO.getTimes() - 1);
            }
        }

        if (ruleDTO.getJudgeType() == Constants.JUDGE_TYPE_DURATION_OR_TIMES) {
            Assert.isTrue(ruleDTO.getTimes() >= 1, "rule的阶梯类型为无阶梯，并且压制类型为告警次数时" +
                    "（times），告警次数必须大于等于1。");
        }
    }
}
